package com.example.poc.food;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.validation.Valid;

import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.poc.exceptions.ResourceNotFoundException;
import com.example.poc.query.SearchRequest;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class FoodController {

  private static final String FOOD_NOT_FOUND = "Food Not Found With Id ";
  private static final String FOOD_DELETED = "Food Deleted With Id ";

  @Autowired
  private FoodService foodService;

  @Autowired
  private FoodModelAssembler foodModelAssembler;

  @Autowired
  private PagedResourcesAssembler<Food> pagedResourcesAssembler;

  @Autowired
  private ModelMapper modelMapper;

  @GetMapping("/v0/foods")
  public List<Food> fetchFoodsAsList() {
    return foodService.fetchFoodDataAsList();
  }

  @GetMapping("/v1/foods")
  public List<Food> fetchFoodsAsFilteredList(@RequestParam(defaultValue = "") String descriptionFilter) {
    return foodService.fetchFilteredFoodDataAsList(descriptionFilter);
  }

  @GetMapping("/v2/foods")
  public Page<Food> fetchFoodsWithPageInterface(@RequestParam(defaultValue = "") String descriptionFilter,
      @RequestParam(defaultValue = "0") int page,
      @RequestParam(defaultValue = "25") int size) {
    return foodService.fetchFoodDataAsPageWithFiltering(descriptionFilter, page, size);
  }

  @GetMapping("/v3/foods")
  public Page<Food> fetchFoodsWithPageInterfaceAndSorted(
    @RequestParam(defaultValue = "") String descriptionFilter,
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "25") int size,
    @RequestParam(defaultValue = "id") String sortColumn,
    @RequestParam(defaultValue = "ASC" ) String sortOrder
  ) {
    return foodService.fetchFoodDataAsPageWithFilteringAndSorting(
      descriptionFilter,
      page,
      size,
      sortColumn,
      sortOrder
    );
  }

  @PostMapping(value = "/v4/foods/search", consumes = MediaType.APPLICATION_JSON_VALUE)
  public PagedModel<FoodModel> search(@RequestBody SearchRequest request) {
    return foodService.searchFoodModel(request);
  }

  @GetMapping(value = "/v4/foods")
  public PagedModel<FoodModel> fetchFoodsWithPagination(
      @RequestParam(defaultValue = "") String descriptionFilter,
      @RequestParam(defaultValue = "0") int page,
      @RequestParam(defaultValue = "25") int size,
      @RequestParam(defaultValue = "id") String sortColumn,
      @RequestParam(defaultValue = "ASC") String sortOrder) {
    Page<Food> foodPage = foodService.fetchFoodDataAsPageWithFilteringAndSorting(
      descriptionFilter,
      page,
      size,
      sortColumn,
      sortOrder
    );
    return pagedResourcesAssembler.toModel(foodPage, foodModelAssembler);
  }

  @GetMapping(value = "/v4/foods/{id}")
  public FoodModel fetchFoodById(@PathVariable Long id) {
    Food food = foodService.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException(FOOD_NOT_FOUND + id.toString()));
    return foodModelAssembler.toModel(food);
  }

  @PostMapping(value = "/v4/foods", consumes = MediaType.APPLICATION_JSON_VALUE)
  public FoodModel saveFood(@Valid @RequestBody FoodDto foodDto) {
    Food food = modelMapper.map(foodDto, Food.class);
    food = foodService.save(food);
    return foodModelAssembler.toModel(food);
  }

  @PatchMapping(value = "/v4/foods/{id}")
  public FoodModel patchFood(@PathVariable Long id, @Valid @RequestBody FoodDtoPatch foodDtoPatch) {
    return foodService.findById(id).map(food -> {
      if (foodDtoPatch.getCode() != null) {
        food.setCode(foodDtoPatch.getCode());
      }
      if (foodDtoPatch.getDescription() != null) {
        food.setDescription(foodDtoPatch.getDescription());
      }
      foodService.save(food);
      return foodModelAssembler.toModel(food);
    }).orElseThrow(() -> new ResourceNotFoundException(FOOD_NOT_FOUND + id.toString()));
  }

  @PutMapping(value = "/v4/foods/{id}")
  public FoodModel putFood(@PathVariable Long id, @Valid @RequestBody FoodDto foodDto) {
    return foodService.findById(id).map(food -> {
      food = modelMapper.map(foodDto, Food.class);
      food.setId(id);
      foodService.save(food);
      return foodModelAssembler.toModel(food);
    }).orElseThrow(() -> new ResourceNotFoundException(FOOD_NOT_FOUND + id.toString()));
  }

  @DeleteMapping(value = "/v4/foods/{id}")
  public ResponseEntity<?> deleteFood(@PathVariable Long id) {
    return foodService.findById(id).map(food -> {
      Map<String, Object> map = new LinkedHashMap<String, Object>();
      FoodModel foodModel = foodModelAssembler.toModel(food);
      map.put("resource", foodModel);
      foodService.delete(food);
      map.put("message", FOOD_DELETED + id.toString());
      return new ResponseEntity<Object>(map, HttpStatus.OK);
    }).orElseThrow(() -> new ResourceNotFoundException(FOOD_NOT_FOUND + id.toString()));
  }

}
